﻿using UnityEngine;
using System.Collections;
using System.Xml;
using System.Collections.Generic;
using System.Linq;
using System;
using UnityEditor;


/// <summary>
/// 加载模型
/// 加载骨骼
/// 加载动画
/// 
/// 选择一个动画
/// PlayAni 进行播放
/// 
/// 将外表面烘培为一个Mesh 减少渲染的三角面数量
/// </summary>
public class LoadModel : MonoBehaviour {
    public TextAsset model;
    public GameObject vertexModel;

    [System.Serializable]
    public struct VetexAttrib
    {
        public Vector3 position;
        public Color color;
    }

    public VetexAttrib []verAttribs;

    [ButtonCallFunc()]
    public bool Load;
    public void LoadMethod()
    {
        var doc = new XmlDocument();
        doc.LoadXml(model.text);

        GameObject.DestroyImmediate(root);
        root = new GameObject("root");
        vertexes.Clear();
        vertexesPos.Clear();

        var allVoxel = doc.GetElementsByTagName("voxels")[0];
        var voxels = allVoxel.ChildNodes;
        Debug.LogError("voxelsCount:" + voxels.Count);
        var c = 0;

        verAttribs = new VetexAttrib[voxels.Count];
        foreach(var v in voxels)
        {
            var xn = (XmlNode)v;
            var x = System.Convert.ToInt32(xn.Attributes["x"].Value);
            var y = System.Convert.ToInt32(xn.Attributes["y"].Value);
            var z = System.Convert.ToInt32(xn.Attributes["z"].Value);
            var r = System.Convert.ToSingle(xn.Attributes["r"].Value);
            var g = System.Convert.ToSingle(xn.Attributes["g"].Value);
            var b = System.Convert.ToSingle(xn.Attributes["b"].Value);
            var a = System.Convert.ToSingle(xn.Attributes["a"].Value);
            verAttribs[c] = new VetexAttrib() { position = new Vector3(x, y, z), color = new Color(r, g, b, a)}; 

            //Debug.Log(x.Value);
            //break;
            //var go = GameObject.CreatePrimitive(PrimitiveType.Cube);
            var go = GameObject.Instantiate<GameObject>(vertexModel); 
            go.name = c + ""; 
            go.transform.parent = root.transform;
            go.transform.localPosition = new Vector3(x, y, z);
            vertexes.Add(go);
            vertexesPos.Add(go.transform.localPosition);
            c++;
        }

    }

    public TextAsset boneFile;
    public GameObject bone;
    public GameObject stick;

    [ButtonCallFunc()]
    public bool LoadBone;
    public void LoadBoneMethod()
    {
        var doc = new XmlDocument();
        doc.LoadXml(boneFile.text);

        GameObject.DestroyImmediate(boneRoot);
        boneRoot = new GameObject("boneRoot");
        idToBone.Clear();
        allBones.Clear();

        var bonePoints = doc.GetElementsByTagName("skeleton")[0];
        foreach(var bp in bonePoints.ChildNodes)
        {
            var bpX = (XmlNode)bp;
            if(bpX.Name == "particle")
            {
                var na = bpX.Attributes["name"].Value;
                var x = Convert.ToSingle(bpX.Attributes["x"].Value);
                var y = Convert.ToSingle(bpX.Attributes["y"].Value);
                var z = Convert.ToSingle(bpX.Attributes["z"].Value);
                var id = bpX.Attributes["id"].Value;

                var go = GameObject.Instantiate(bone) as GameObject;
                go.name = na+"_"+id;
                go.transform.parent = boneRoot.transform;
                go.transform.localPosition = new Vector3(x, y, z);
                idToBone[id] = go;
                allBones.Add(go);
                tPose.Add(go.transform.localPosition);
            }
        }


        idToStick.Clear();
        foreach(var bp in bonePoints.ChildNodes)
        {
            var bpX = (XmlNode)bp;
            if(bpX.Name == "stick")
            {
                var a = bpX.Attributes["a"].Value;
                var b = bpX.Attributes["b"].Value;
                var ba = idToBone[a];
                var bb = idToBone[b];
                var go = new GameObject("stick_" + ba.name + "_" + bb.name+"_"+idToStick.Count);
                var bcom = go.AddComponent<Bone>();
                go.transform.parent = boneRoot.transform;
                bcom.stick = stick;
                bcom.SetBone(ba, bb);
                idToStick.Add(go);
            }
        }

        vertexGroup.Clear();
        //确定 骨骼 和 顶点的绑定关系
        var bindings = doc.GetElementsByTagName("skeletonVoxelBindings")[0];
        Debug.LogError("VertexCount:"+vertexes.Count+":"+bindings.ChildNodes.Count);
        foreach(var g in bindings.ChildNodes)
        {
            var gx = (XmlNode)g;
            var groupId = gx.Attributes["constraintIndex"].Value;
            foreach(var v in gx.ChildNodes)
            {
                var vx = (XmlNode)v;
                var vid = Convert.ToInt32(vx.Attributes["index"].Value);
                if(vid >= vertexes.Count)
                {
                    Debug.LogError("VerId Out:" + vid);
                    return;
                }
                var verObj = vertexes[vid];
                if(!vertexGroup.ContainsKey(groupId))
                {
                    Debug.LogError("AddGroup:" + groupId);
                    vertexGroup[groupId] = new List<GameObject>();
                }
                vertexGroup[groupId].Add(verObj);

                if(!vertexToBone.ContainsKey(vid))
                {
                    vertexToBone[vid] = new List<GameObject>();
                }
                var gid = Convert.ToInt32(groupId);
                if (idToStick.Count > gid)
                {
                    vertexToBone[vid].Add(idToStick[gid]);
                }
            }

        }

    }


    public bool SelBoneMode = false;
    public GameObject selBone;

    //选择了Bone 接着Move的时候Update限制Bone的距离
    [ButtonCallFunc()]
    public bool SelectBone;
    public void SelectBoneMethod()
    {
        if (EditorApplication.isPlaying)
        {
            if (SelBoneMode)
            {
                var bId = selBone.name.Split('_')[5];
                Debug.LogError("SelectBone:" + bId + ":" + vertexGroup.Count);
                var vg = vertexGroup[bId];
                foreach (var v in vg)
                {
                    v.GetComponent<MeshRenderer>().material.color = Color.yellow;
                }
            }
            else
            {
                foreach (var v in vertexes)
                {
                    var mr = v.GetComponent<MeshRenderer>();
                    mr.material.color = Color.blue;
                }
            }
        }else
        {
            Debug.LogError("OnlyPlayer Can Change Material");
        }

    }

    public TextAsset animationFile;
    [ButtonCallFunc()]
    public bool LoadAni;
    public void LoadAniMethod()
    {
        var doc = new XmlDocument();
        doc.LoadXml(animationFile.text);
        var anis = doc.GetElementsByTagName("animation");
        animations.Clear();

        foreach(var a in anis)
        {
            var ani = new List<List<Vector3>>();
            animations.Add(ani);

            var ax = (XmlNode)a;
            foreach(var f in ax.ChildNodes)
            {
                var frame = new List<Vector3>();
                ani.Add(frame);

                var fx = (XmlNode)f;
                for(var i = 0; i < fx.ChildNodes.Count; i++)
                {
                    var bp = fx.ChildNodes[i];
                    if (bp.Name == "position")
                    {
                        var x = Convert.ToSingle(bp.Attributes["x"].Value);
                        var y = Convert.ToSingle(bp.Attributes["y"].Value);
                        var z = Convert.ToSingle(bp.Attributes["z"].Value);
                        //allBones[i].transform.localPosition = new Vector3(x, y, z);
                        frame.Add(new Vector3(x, y, z));
                    }
                }
            }
            //break;
        }
    }

    public int aniIndex = 0;
    public int frameIndex = 0;
    [ButtonCallFunc()]
    public bool RenderAni;
    public void RenderAniMethod()
    {
        var ani = animations[aniIndex];
        var frame = ani[frameIndex];
        for(var i = 0; i < frame.Count; i++)
        {
            allBones[i].transform.localPosition = frame[i];
        }
        foreach(var s in idToStick)
        {
            s.GetComponent<Bone>().ResetPos();
        }

        aniVertexPos = new Vector3[vertexesPos.Count];

        for (var i = 0; i < idToStick.Count; i++)
        {
            var stick = idToStick[i];
            var vg = vertexGroup[i.ToString()];
            var sb = stick.GetComponent<Bone>();
            sb.CalculateQua();
            for (var j = 0; j < vg.Count; j++)
            {
                var vId = Convert.ToInt32(vg[j].name);
                var vp = vertexesPos[vId];
                sb.CalculateVertex(vp, vg[j]);
                aniVertexPos[vId] = vg[j].transform.position;
            }
        }
    }

    [ButtonCallFunc()]
    public bool ResetAni;
    public void ResetAniMethod()
    {
        for(var i = 0; i < allBones.Count; i++)
        {
            allBones[i].transform.localPosition = tPose[i];
        }

        foreach (var s in idToStick)
        {
            s.GetComponent<Bone>().ResetPos();
        }
        for (var i = 0; i < idToStick.Count; i++)
        {
            var stick = idToStick[i];
            var vertexs = vertexGroup[i.ToString()];
            var sb = stick.GetComponent<Bone>();
            sb.CalculateQua();
            for (var j = 0; j < vertexs.Count; j++)
            {
                var vId = Convert.ToInt32(vertexs[j].name);
                var vp = vertexesPos[vId];
                sb.CalculateVertex(vp, vertexs[j]);
            }
        }

    }

    [ButtonCallFunc()]
    public bool PlayAni;
    public void PlayAniMethod()
    {
        var ani = animations[aniIndex];
        var fn = ani.Count;
        frameIndex %= fn;

        RenderAniMethod(); 
        frameIndex++;
        frameIndex %= fn;
    }

    [ButtonCallFunc()]
    public bool Clear;
    public void ClearMethod()
    {
        GameObject.DestroyImmediate(root);
        GameObject.DestroyImmediate(boneRoot);
        GameObject.DestroyImmediate(bakeObj);
        idToBone.Clear();
        allBones.Clear();
        idToStick.Clear();
        vertexGroup.Clear();
        vertexToBone.Clear();
        vertexes.Clear();
        vertexesPos.Clear();
        animations.Clear();
        tPose.Clear();
    }

    [ButtonCallFunc()]
    public bool BakeMesh;
    public void BakeMeshMethod()
    {
        GameObject.DestroyImmediate(bakeObj);
        bakeObj.transform.position = new Vector3(200, 0, 0);
        bakeObj.AddComponent<MeshFilter>();
        bakeObj.AddComponent<MeshRenderer>();

        bakeMesh = new Mesh();

        boundMin = new Vector3(999, 999, 999);
        boundMax = new Vector3(-999, -999, -999);
        foreach(var v in vertexes)
        {
            boundMin = Vector3.Min(v.transform.localPosition, boundMin);
            boundMax = Vector3.Max(v.transform.localPosition, boundMax);
        }

        boundSize = boundMax - boundMin + Vector3.one;
        voxelCell = new GameObject[(int)(boundSize.x * boundSize.y * boundSize.z)];
        foreach(var v in vertexes)
        {
            var relativePos = v.transform.localPosition - boundMin;
            var storeInd = (int)(relativePos.x * (boundSize.y * boundSize.z) + relativePos.y * (boundSize.z) + relativePos.z);
            voxelCell[storeInd] = v;
        }

        //填充内部 便于查找边界
        FloodFill();
        /*
        var keepFace = new List<Face>();
        foreach(var v in vertexes)
        {
            var relativePos = v.transform.localPosition - boundMin;
            var keep = CheckUp(relativePos);
            
        }
        */
    }
    private void FloodFill()
    {
        var zmid = Mathf.Floor(boundSize.z / 2);
        var ymid = Mathf.Floor(boundSize.y / 2);
        var xSeed = 0;
        //FindFace
        //FindInner voxel
        var findFace = false;
        for(var x = 0; x < boundSize.x; x++)
        {
            var storeId = (int)(x * (boundSize.y * boundSize.z) + ymid * boundSize.z + zmid);
            if(voxelCell[storeId] != null)
            {
                if(!findFace)
                {
                    findFace = true;
                }
            }else
            {
                if(findFace)
                {
                    xSeed = x;
                    break;
                }
            }
        }

        var openList = new Queue<Vector3>();
        openList.Enqueue(new Vector3(xSeed, ymid, zmid));
        //用renderdoc 之类的调试一下
        while(openList.Count > 0)
        {
            var seed = openList.Dequeue();
            var xl = seed + new Vector3(-1, 0, 0);
            var xu = seed + new Vector3(1, 0, 0);
            var yl = seed + new Vector3(0, -1, 0);
            var yu = seed + new Vector3(0, 1, 0);
            var zl = seed + new Vector3(0, 0, -1);
            var zu = seed + new Vector3(0, 0, 1);

            //if(CheckHasCell())

        }
    }
    private bool CheckHasCell(Vector3 relativePos)
    {
        var storeInd = (int)(relativePos.x * (boundSize.y * boundSize.z) + (relativePos.y) * (boundSize.z) + relativePos.z);
        return voxelCell[storeInd] != null;
    }

    private bool CheckUp(Vector3 relativePos)
    {
        var storeInd = (int)(relativePos.x * (boundSize.y * boundSize.z) + (relativePos.y+1) * (boundSize.z) + relativePos.z);
        return voxelCell[storeInd] == null;
    }

    //左手 顺时针 坐标系统
    public struct Face
    {
        public Vector3 a, b, c, d;
    };

    private Vector3 boundMin, boundMax, boundSize;
    private GameObject[] voxelCell;

    public Mesh bakeMesh;
    private GameObject bakeObj;

    public GameObject root;
    public GameObject boneRoot;
    private Dictionary<string, GameObject> idToBone = new Dictionary<string, GameObject>();
    public List<GameObject> allBones = new List<GameObject>();
    public List<GameObject> idToStick = new List<GameObject>();

    public Dictionary<string, List<GameObject>> vertexGroup = new Dictionary<string, List<GameObject>>();
    private Dictionary<int, List<GameObject>> vertexToBone = new Dictionary<int, List<GameObject>>();
    public List<GameObject> vertexes = new List<GameObject>();
    private List<Vector3> vertexesPos = new List<Vector3>();

    public Vector3[] aniVertexPos;


    private List<List<List<Vector3>>> animations = new List<List<List<Vector3>>>();
    private List<Vector3> tPose = new List<Vector3>();

}
